package com.happy3w.ssp.schoolmatehome.service.auth;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.happy3w.ssp.schoolmatehome.component.stub.StubConfig;
import com.happy3w.ssp.schoolmatehome.component.stub.StubItem;
import com.happy3w.ssp.schoolmatehome.component.wx.UserInfoResponse;
import com.happy3w.ssp.schoolmatehome.component.wx.WeChatService;
import com.happy3w.ssp.schoolmatehome.component.wx.WeChatSession;
import com.happy3w.ssp.schoolmatehome.entity.User;
import com.happy3w.ssp.schoolmatehome.exception.UnauthorizedException;
import com.happy3w.ssp.schoolmatehome.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Slf4j
@RequiredArgsConstructor
@Service
public class SessionService {

    private final WeChatService weChatService;
    private final UserRepository userRepository;
    private final StubConfig stubConfig;
    private final PasswordEncoder passwordEncoder;

    private final Cache<String, SessionHolder> jumpSessionCache = CacheBuilder.newBuilder()
            .expireAfterWrite(1, TimeUnit.MINUTES)
            .maximumSize(1000)
            .removalListener(new RemovalListener<String, SessionHolder>() {
                @Override
                public void onRemoval(RemovalNotification<String, SessionHolder> notification) {
                    SessionHolder holder = notification.getValue();
                    idJumpCodeCache.remove(holder.id);
                }
            })
            .build();
    private final Map<String, String> idJumpCodeCache = new HashMap<>();

    public SessionInfo loginByCode(String code) {
        StubItem si = stubConfig.findItem(code);
        WeChatSession weChatSession = si == null
                ? weChatService.getWebSession(code)
                : WeChatSession.builder()
                .openid(si.getOpenId())
                .build();

        if (weChatSession == null) {
            throw new UnauthorizedException("获取openid失败");
        }
        UserInfoResponse userInfo = weChatService.getUserInfo(weChatSession);
        User user = userRepository.findByWxUnionId(userInfo.getUnionid())
                .map(u -> updateUserInfo(u, userInfo))
                .orElseGet(() -> newUser(userInfo));
        log.info("web login user: {}, {}", user.getId(), user.getName());
        return SessionInfo.build(user);
    }

    private User updateUserInfo(User u, UserInfoResponse userInfo) {
        if (!Objects.equals(u.getWxName(), userInfo.getNickname())
            || !Objects.equals(u.getAvatar(), userInfo.getHeadimgurl())) {
            if (Objects.equals(u.getWxName(), u.getName())) {
                u.setName(userInfo.getNickname());
            }
            u.setWxName(userInfo.getNickname());
            u.setAvatar(userInfo.getHeadimgurl());
            u.setUpdatedAt(new Date());
            userRepository.save(u);
        }
        return u;
    }

    private User newUser(UserInfoResponse userInfo) {
        return userRepository.save(User.builder()
                .wxUnionId(userInfo.getUnionid())
                .wxOpenId(userInfo.getOpenid())
                .wxName(userInfo.getNickname())
                .name(userInfo.getNickname())
                .avatar(userInfo.getHeadimgurl())
                .joinInAt(new Date())
                .enabled(true)
                .createdAt(new Date())
                .updatedAt(new Date())
                .build());
    }

    public SessionInfo loginByPhone(String phone, String password) {
        User user = userRepository.findByPhone(phone)
                .orElseThrow(() -> new UnauthorizedException("手机号或密码错误"));
        return loginByCode(user, password);
    }

    public SessionInfo loginByEmail(String email, String password) {
        User user = userRepository.findByEmail(email)
            .orElseThrow(() -> new UnauthorizedException("邮箱或密码错误"));
        return loginByCode(user, password);
    }

    private SessionInfo loginByCode(User user, String password) {
        String salt = user.getSalt();
        if (!passwordEncoder.matches(password + salt,
            user.getPasswordHash())) {
            throw new UnauthorizedException("账号或密码错误");
        }

        log.info("phone login user: {}, {}", user.getId(), user.getName());
        return SessionInfo.build(user);
    }

    public String prepareLoginMini(String id) {
        String existJumpCode = idJumpCodeCache.get(id);
        if (StringUtils.hasText(existJumpCode)) {
            jumpSessionCache.invalidate(existJumpCode);
        }

        String jumpCode = UUID.randomUUID().toString();
        SessionHolder holder = new SessionHolder();
        holder.id = id;
        holder.jumpCode = jumpCode;

        idJumpCodeCache.put(id, jumpCode);
        jumpSessionCache.put(jumpCode, holder);
        return jumpCode;
    }

    public SessionInfo loginMini(String code, String nickName, String avatarUrl, String jumpCode) {
        WeChatSession weChatSession = weChatService.getMiniSession(code);

        if (weChatSession == null) {
            throw new UnauthorizedException("获取openid失败");
        }
        UserInfoResponse userInfo = UserInfoResponse.builder()
                .unionid(weChatSession.getUnionid())
                .openid(weChatSession.getOpenid())
                .nickname(nickName)
                .headimgurl(avatarUrl)
                .build();
        User user = userRepository.findByWxUnionId(userInfo.getUnionid())
                .map(u -> updateUserInfo(u, userInfo))
                .orElseGet(() -> newUser(userInfo));

        SessionInfo sessionInfo = SessionInfo.build(user);
        if (StringUtils.hasText(jumpCode)) {
            SessionHolder holder = jumpSessionCache.getIfPresent(jumpCode);
            if (holder != null) {
                holder.sessionInfo = sessionInfo;
            }
        }

        log.info("mini login user: {}, {}", user.getId(), user.getName());
        return sessionInfo;
    }

    public SessionInfo checkLoginStatus(String jumpCode) {
        SessionHolder holder = jumpSessionCache.getIfPresent(jumpCode);
        if (holder == null) {
            return null;
        }

        if (holder.sessionInfo == null) {
            return null;
        }

        SessionInfo sessionInfo = holder.sessionInfo;
        jumpSessionCache.invalidate(jumpCode);
        return sessionInfo;
    }

    public SessionInfo renew(User user) {
        return SessionInfo.build(user);
    }


    private static class SessionHolder {
        private String id;
        private String jumpCode;
        private SessionInfo sessionInfo;
    }
}
